"""
修复版普通话声调评分工具
主程序入口
"""
import tkinter as tk
from tkinter import ttk, filedialog, messagebox
import matplotlib.pyplot as plt
from matplotlib.backends.backend_tkagg import FigureCanvasTkAgg
import matplotlib
import numpy as np
import os
import json
import shutil
from datetime import datetime
import threading
import time

# 设置中文字体
matplotlib.rcParams['font.sans-serif'] = ['SimHei', 'Microsoft YaHei', 'Arial Unicode MS']
matplotlib.rcParams['axes.unicode_minus'] = False

# 导入依赖包
try:
    import sounddevice as sd
    import soundfile as sf
    import parselmouth
    import pywt
    from scipy.optimize import curve_fit
    DEPENDENCIES_OK = True
except ImportError as e:
    DEPENDENCIES_OK = False
    print(f"依赖包导入失败: {e}")

# 真实音频处理类
class PraatProcessor:
    def __init__(self, f0_min=80, f0_max=400, time_step=0.01):
        self.f0_min = f0_min
        self.f0_max = f0_max
        self.time_step = time_step
    
    def extract_f0_contour(self, audio_path, normalize=False, smooth=True):
        """提取基频轮廓"""
        try:
            # 读取音频文件
            sound = parselmouth.Sound(audio_path)
            
            # 提取基频
            pitch = sound.to_pitch(
                time_step=self.time_step,
                pitch_floor=self.f0_min,
                pitch_ceiling=self.f0_max
            )
            
            # 获取基频值
            f0_values = pitch.selected_array['frequency']
            time_values = pitch.xs()
            
            # 移除未定义的基频值 (NaN) 和异常值
            valid_indices = ~np.isnan(f0_values)
            time_clean = time_values[valid_indices]
            f0_clean = f0_values[valid_indices]
            
            if len(f0_clean) == 0:
                return np.array([]), np.array([])
            
            # 第一步：基本范围过滤（使用Praat设置的范围）
            freq_valid_indices = (f0_clean >= self.f0_min) & (f0_clean <= self.f0_max)
            time_clean = time_clean[freq_valid_indices]
            f0_clean = f0_clean[freq_valid_indices]
            
            if len(f0_clean) == 0:
                print(f"警告: 基本过滤后没有有效基频数据。范围: {self.f0_min}-{self.f0_max}Hz")
                return np.array([]), np.array([])
            
            print(f"基本过滤后: {len(f0_clean)}个数据点, 范围: {np.min(f0_clean):.1f}-{np.max(f0_clean):.1f}Hz")
            
            # 第二步：更严格的正常人声范围过滤
            # 正常人声基频范围：男性70-250Hz, 女性120-400Hz, 这里取保守范围60-350Hz
            human_voice_min, human_voice_max = 60, 350
            human_voice_indices = (f0_clean >= human_voice_min) & (f0_clean <= human_voice_max)
            
            if np.sum(~human_voice_indices) > 0:
                outliers = f0_clean[~human_voice_indices]
                print(f"发现{np.sum(~human_voice_indices)}个异常基频值: {outliers}")
            
            time_clean = time_clean[human_voice_indices]
            f0_clean = f0_clean[human_voice_indices]
            
            if len(f0_clean) == 0:
                print(f"警告: 人声范围过滤后没有有效数据。正常范围: {human_voice_min}-{human_voice_max}Hz")
                return np.array([]), np.array([])
            
            # 第三步：统计异常值检测和过滤
            if len(f0_clean) > 5:  # 有足够数据点才进行统计分析
                median_f0 = np.median(f0_clean)
                mad = np.median(np.abs(f0_clean - median_f0))  # 中位数绝对偏差
                
                # 使用改进的MAD方法检测异常值（比3σ规则更鲁棒）
                threshold = 3.0  # 阈值系数
                lower_bound = median_f0 - threshold * mad * 1.4826  # 1.4826是MAD的缩放因子
                upper_bound = median_f0 + threshold * mad * 1.4826
                
                # 确保边界在合理范围内
                lower_bound = max(lower_bound, human_voice_min)
                upper_bound = min(upper_bound, human_voice_max)
                
                statistical_valid_indices = (f0_clean >= lower_bound) & (f0_clean <= upper_bound)
                
                if np.sum(~statistical_valid_indices) > 0:
                    print(f"统计异常值检测: 中位数={median_f0:.1f}Hz, MAD={mad:.1f}, 过滤范围={lower_bound:.1f}-{upper_bound:.1f}Hz")
                    print(f"过滤掉{np.sum(~statistical_valid_indices)}个统计异常值")
                
                time_clean = time_clean[statistical_valid_indices]
                f0_clean = f0_clean[statistical_valid_indices]
            
            if len(f0_clean) == 0:
                print(f"警告: 统计过滤后没有有效数据")
                return np.array([]), np.array([])
            
            # 第四步：连续性检查和突变过滤
            if len(f0_clean) > 3:
                # 计算相邻点之间的基频变化率
                f0_diff = np.abs(np.diff(f0_clean))
                median_diff = np.median(f0_diff)
                
                # 定义突变阈值：超过中位数变化率的5倍被认为是突变
                jump_threshold = max(median_diff * 5, 20)  # 至少20Hz的阈值
                jump_indices = np.where(f0_diff > jump_threshold)[0]
                
                if len(jump_indices) > 0:
                    print(f"发现{len(jump_indices)}个基频突变点，阈值={jump_threshold:.1f}Hz")
                    
                    # 保守策略：移除突变点后的数据（通常是噪音引起的）
                    valid_mask = np.ones(len(f0_clean), dtype=bool)
                    for jump_idx in jump_indices:
                        # 检查突变是上升还是下降
                        if jump_idx + 1 < len(f0_clean):
                            if f0_clean[jump_idx + 1] > f0_clean[jump_idx] * 1.5:  # 上升突变
                                # 移除突变点之后的数据，直到基频回到正常范围
                                for i in range(jump_idx + 1, len(f0_clean)):
                                    if f0_clean[i] > f0_clean[jump_idx] * 1.2:  # 仍然异常高
                                        valid_mask[i] = False
                                    else:
                                        break
                    
                    time_clean = time_clean[valid_mask]
                    f0_clean = f0_clean[valid_mask]
                    print(f"连续性过滤后保留: {len(f0_clean)}个数据点")
            
            if len(f0_clean) == 0:
                print(f"警告: 连续性过滤后没有有效数据")
                return np.array([]), np.array([])
            
            print(f"最终有效基频: {len(f0_clean)}个数据点, 范围: {np.min(f0_clean):.1f}-{np.max(f0_clean):.1f}Hz")
            
            # 仅使用中位数滤波去除突变点，完全保留声调特征
            if smooth and len(f0_clean) > 3:
                try:
                    from scipy import signal
                    # 中位数滤波去除异常值，但保留声调轮廓
                    kernel_size = min(3, len(f0_clean) if len(f0_clean) % 2 == 1 else len(f0_clean) - 1)
                    if kernel_size >= 3:
                        f0_clean = signal.medfilt(f0_clean, kernel_size=kernel_size)
                        print(f"仅中位数滤波: kernel_size={kernel_size}, 完全保留声调特征")
                        print(f"处理后基频范围: {np.min(f0_clean):.1f}-{np.max(f0_clean):.1f}Hz")
                except ImportError:
                    print("scipy不可用，跳过滤波处理")
                    pass
            
            return time_clean, f0_clean
            
        except Exception as e:
            print(f"提取基频时发生错误: {e}")
            return np.array([]), np.array([])

# 真实曲线拟合类
class ToneCurveFitter:
    def __init__(self):
        self.fitting_methods = {
            1: self._fit_quadratic, # 一声：二次函数
            2: self._fit_quadratic, # 二声：二次函数  
            3: self._fit_quadratic, # 三声：二次函数
            4: self._fit_quadratic  # 四声：二次函数
        }
    
    def _linear_function(self, x, a, b):
        return a * x + b
    
    def _quadratic_function(self, x, a, b, c):
        return a * x**2 + b * x + c
    
    def _fit_linear(self, time_array, f0_array):
        try:
            time_normalized = (time_array - time_array.min()) / (time_array.max() - time_array.min())
            popt, pcov = curve_fit(self._linear_function, time_normalized, f0_array)
            a, b = popt
            
            y_pred = self._linear_function(time_normalized, a, b)
            r_squared = 1 - np.sum((f0_array - y_pred)**2) / np.sum((f0_array - np.mean(f0_array))**2)
            
            return {
                'type': 'linear',
                'parameters': {'a': a, 'b': b},
                'function': self._linear_function,
                'r_squared': r_squared,
                'success': True,
                'time_normalized': time_normalized,
                'fitted_values': y_pred
            }
        except Exception as e:
            return {
                'type': 'linear',
                'parameters': {'a': 0, 'b': np.mean(f0_array) if len(f0_array) > 0 else 0},
                'success': False,
                'error': str(e)
            }
    
    def _fit_quadratic(self, time_array, f0_array):
        try:
            time_normalized = (time_array - time_array.min()) / (time_array.max() - time_array.min())
            popt, pcov = curve_fit(self._quadratic_function, time_normalized, f0_array)
            a, b, c = popt
            
            y_pred = self._quadratic_function(time_normalized, a, b, c)
            r_squared = 1 - np.sum((f0_array - y_pred)**2) / np.sum((f0_array - np.mean(f0_array))**2)
            
            return {
                'type': 'quadratic',
                'parameters': {'a': a, 'b': b, 'c': c},
                'function': self._quadratic_function,
                'r_squared': r_squared,
                'success': True,
                'time_normalized': time_normalized,
                'fitted_values': y_pred
            }
        except Exception as e:
            return {
                'type': 'quadratic',
                'parameters': {'a': 0, 'b': 0, 'c': np.mean(f0_array) if len(f0_array) > 0 else 0},
                'success': False,
                'error': str(e)
            }
    
    def fit_tone_curve(self, time_array, f0_array, tone):
        if len(time_array) == 0 or len(f0_array) == 0:
            return {'type': 'unknown', 'parameters': {}, 'success': False, 'error': '输入数据为空'}
        
        if tone not in self.fitting_methods:
            return {'type': 'unknown', 'parameters': {}, 'success': False, 'error': f'不支持的声调: {tone}'}
        
        fitting_method = self.fitting_methods[tone]
        result = fitting_method(time_array, f0_array)
        result['tone'] = tone
        return result
    
    def generate_sample_points(self, fitting_result, num_points=20):
        """从拟合结果生成最优分布的采样点（基于声调特征的智能采样）"""
        if not fitting_result.get('success', False):
            return np.array([])
        
        tone = fitting_result.get('tone', 1)
        
        # 根据声调类型选择不同的采样策略
        if fitting_result['type'] == 'quadratic':
            # 二次函数：主要用于三声
            a = fitting_result['parameters']['a']
            b = fitting_result['parameters']['b']
            c = fitting_result['parameters']['c']
            
            # 计算极值点：x = -b/(2a)
            if a != 0:
                extremum_x = -b / (2 * a)
                extremum_x = max(0.05, min(0.95, extremum_x))  # 确保在合理范围内
            else:
                extremum_x = 0.5
            
            # 计算极值点处的基频值
            extremum_y = a * extremum_x**2 + b * extremum_x + c
            
            # 计算起点和终点的基频值
            start_y = c
            end_y = a + b + c
            
            # 计算基频变化幅度来确定采样密度
            total_range = abs(max(start_y, end_y, extremum_y) - min(start_y, end_y, extremum_y))
            
            # 策略：基于三段式采样
            # 起始段(0-0.35)：捕捉下降趋势
            # 转折段(0.35-0.65)：密集采样捕捉转折点
            # 上升段(0.65-1.0)：捕捉上升趋势
            
            # 动态调整转折段范围，基于极值点位置
            transition_start = max(0.2, extremum_x - 0.15)
            transition_end = min(0.8, extremum_x + 0.15)
            
            # 分配采样点：起始段25%，转折段50%，上升段25%
            start_count = max(2, int(num_points * 0.25))
            transition_count = max(4, int(num_points * 0.5))
            end_count = num_points - start_count - transition_count
            
            # 三段采样
            start_points = np.linspace(0.0, transition_start, start_count + 1)[:-1]  # 不包含端点
            transition_points = np.linspace(transition_start, transition_end, transition_count + 2)[1:-1]  # 不包含两端
            end_points = np.linspace(transition_end, 1.0, end_count + 1)[1:]  # 不包含起点
            
            # 合并所有采样点
            x_sample = np.concatenate([start_points, transition_points, end_points])
            x_sample = np.sort(x_sample)
            
            print(f"三声智能采样: 极值点={extremum_x:.3f}, 转折段[{transition_start:.2f}, {transition_end:.2f}], 分段采样({len(start_points)}-{len(transition_points)}-{len(end_points)})")
            
        else:
            # 线性函数：一、二、四声的不同策略
            if tone == 1:
                # 一声(阴平)：平调，起始和结束稍微密集
                start_dense = np.linspace(0.0, 0.2, 4)
                middle_sparse = np.linspace(0.25, 0.75, num_points - 8)
                end_dense = np.linspace(0.8, 1.0, 4)
                x_sample = np.concatenate([start_dense, middle_sparse, end_dense])
                print(f"一声采样: 起始密集(4点) + 中间稀疏({len(middle_sparse)}点) + 结束密集(4点)")
                
            elif tone == 2:
                # 二声(阳平)：上升调，偏向后半段
                front_sparse = np.linspace(0.0, 0.4, int(num_points * 0.3))
                back_dense = np.linspace(0.45, 1.0, num_points - len(front_sparse))
                x_sample = np.concatenate([front_sparse, back_dense])
                print(f"二声采样: 前段稀疏({len(front_sparse)}点) + 后段密集({len(back_dense)}点)")
                
            elif tone == 4:
                # 四声(去声)：下降调，偏向前半段
                front_dense = np.linspace(0.0, 0.55, int(num_points * 0.7))
                back_sparse = np.linspace(0.6, 1.0, num_points - len(front_dense))
                x_sample = np.concatenate([front_dense, back_sparse])
                print(f"四声采样: 前段密集({len(front_dense)}点) + 后段稀疏({len(back_sparse)}点)")
                
            else:
                # 默认均匀采样
                x_sample = np.linspace(0, 1, num_points)
                print(f"默认均匀采样: {num_points}点")
            
            x_sample = np.sort(x_sample)
        
        # 计算对应的y值
        if fitting_result['type'] == 'linear':
            params = fitting_result['parameters']
            y_sample = self._linear_function(x_sample, params['a'], params['b'])
        elif fitting_result['type'] == 'quadratic':
            params = fitting_result['parameters']
            y_sample = self._quadratic_function(x_sample, params['a'], params['b'], params['c'])
        else:
            return np.array([])
        
        # 返回时间和基频坐标，便于后续处理
        return {
            'time_sample': x_sample,
            'f0_sample': y_sample,
            'sample_points': y_sample  # 保持向后兼容
        }
    
    def normalize_to_01(self, y_values):
        """将数据归一化到0-1范围（简单线性归一化）"""
        if len(y_values) == 0:
            return np.array([])
        
        y_min = np.min(y_values)
        y_max = np.max(y_values)
        
        # 避免除零错误
        if y_max - y_min == 0:
            return np.ones_like(y_values) * 0.5  # 常数序列归一化到0.5
        
        normalized = (y_values - y_min) / (y_max - y_min)
        return normalized
    
    def normalize_to_semitones(self, y_values, ref_freq=None):
        """
        将基频数据转换为半音单位（对数归一化）
        基于人耳听觉感知的对数特性
        
        公式: semitones = 12 * log2(f / f_ref)
        
        Args:
            y_values: 基频序列 (Hz)
            ref_freq: 参考频率 (Hz)，如果为None则使用序列的最小值
        
        Returns:
            以半音为单位的相对音高序列
        """
        if len(y_values) == 0:
            return np.array([])
        
        # 过滤掉无效值
        valid_freqs = y_values[y_values > 0]
        if len(valid_freqs) == 0:
            return np.zeros_like(y_values)
        
        # 选择参考频率
        if ref_freq is None:
            ref_freq = np.min(valid_freqs)
        
        # 确保参考频率有效
        if ref_freq <= 0:
            ref_freq = 80  # 使用默认的最低人声频率
        
        # 计算半音差
        # 公式: semitones = 12 * log2(f / f_ref)
        semitones = 12 * np.log2(y_values / ref_freq)
        
        print(f"半音归一化: 参考频率={ref_freq:.1f}Hz, 输入范围={np.min(y_values):.1f}-{np.max(y_values):.1f}Hz")
        print(f"半音范围: {np.min(semitones):.2f}-{np.max(semitones):.2f}半音")
        
        return semitones

# 音频播放器类
class AudioPlayer:
    def __init__(self):
        self.is_playing = False
        self.playback_thread = None
    
    def play_audio(self, audio_path):
        """播放音频文件"""
        if not audio_path or not os.path.exists(audio_path):
            print(f"音频文件不存在: {audio_path}")
            return False
        
        try:
            import soundfile as sf
            import sounddevice as sd
            
            # 停止当前播放
            self.stop_audio()
            
            # 读取音频文件
            data, sample_rate = sf.read(audio_path)
            print(f"音频文件信息: 形状={data.shape}, 采样率={sample_rate}")
            
            # 播放音频
            self.is_playing = True
            print(f"开始播放音频: {os.path.basename(audio_path)}")
            
            # 使用阻塞播放方式，在单独线程中执行
            def play_in_thread():
                try:
                    sd.play(data, sample_rate)
                    sd.wait()  # 等待播放完成
                    self.is_playing = False
                    print("音频播放完成")
                except Exception as e:
                    print(f"播放过程中出错: {e}")
                    self.is_playing = False
            
            self.playback_thread = threading.Thread(target=play_in_thread, daemon=True)
            self.playback_thread.start()
            
            return True
            
        except Exception as e:
            print(f"播放音频失败: {e}")
            import traceback
            traceback.print_exc()
            self.is_playing = False
            return False
    
    def stop_audio(self):
        """停止播放音频"""
        try:
            import sounddevice as sd
            if self.is_playing:
                sd.stop()
                self.is_playing = False
                print("音频播放已停止")
        except Exception as e:
            print(f"停止播放失败: {e}")
    
    def is_audio_playing(self):
        """检查是否正在播放"""
        return self.is_playing

# 真实DWT计算类
class AudioRecorder:
    def __init__(self, sample_rate=44100, channels=1):
        self.sample_rate = sample_rate
        self.channels = channels
        self.recording = None
        self.is_recording = False
        
    def start_recording(self):
        """开始录音"""
        import sounddevice as sd
        import soundfile as sf
        
        self.recording = []
        self.is_recording = True
        
        def audio_callback(indata, frames, time, status):
            if self.is_recording:
                self.recording.append(indata.copy())
        
        self.stream = sd.InputStream(
            samplerate=self.sample_rate,
            channels=self.channels,
            callback=audio_callback
        )
        self.stream.start()
        
    def stop_recording(self):
        """停止录音并保存文件"""
        if not self.is_recording:
            return None
            
        self.is_recording = False
        self.stream.stop()
        self.stream.close()
        
        if not self.recording:
            return None
            
        # 合并录音数据
        import numpy as np
        import soundfile as sf
        import os
        from datetime import datetime
        
        audio_data = np.concatenate(self.recording, axis=0)
        
        # 确保temp_audio目录存在
        temp_dir = "temp_audio"
        if not os.path.exists(temp_dir):
            os.makedirs(temp_dir)
        
        # 生成文件名
        timestamp = datetime.now().strftime("%Y%m%d_%H%M%S")
        filename = f"user_recording_{timestamp}.wav"
        filepath = os.path.join(temp_dir, filename)
        
        # 保存音频文件
        sf.write(filepath, audio_data, self.sample_rate)
        
        return filepath

class DWTCalculator:
    def __init__(self, wavelet='db4', levels=3):
        self.wavelet = wavelet
        self.levels = levels
    
    def dwt_distance(self, signal1, signal2, use_weights=True, mode='symmetric'):
        try:
            print(f"原始信号: signal1 范围={np.min(signal1):.1f}-{np.max(signal1):.1f}, signal2 范围={np.min(signal2):.1f}-{np.max(signal2):.1f}")
            
            # 删除归一化，直接使用原始信号进行DWT计算
            signal1_processed = signal1
            signal2_processed = signal2
            
            # 对齐信号长度
            max_length = max(len(signal1_processed), len(signal2_processed))
            signal1_padded = np.pad(signal1_processed, (0, max_length - len(signal1_processed)), mode='symmetric')
            signal2_padded = np.pad(signal2_processed, (0, max_length - len(signal2_processed)), mode='symmetric')
            
            # 小波分解 - 使用symmetric模式减少边界效应
            # symmetric: 镜像延拓，避免边界处突变，更适合连续语音信号
            coeffs1 = pywt.wavedec(signal1_padded, self.wavelet, mode=mode, level=self.levels)
            coeffs2 = pywt.wavedec(signal2_padded, self.wavelet, mode=mode, level=self.levels)
            
            # 计算距离
            total_distance = 0.0
            total_weight = 0.0
            # 调整权重，降低对高频细节的敏感度
            weights = [1.2] + [0.8] * (len(coeffs1) - 1) if use_weights else [1.0] * len(coeffs1)
            
            for i, (c1, c2) in enumerate(zip(coeffs1, coeffs2)):
                min_len = min(len(c1), len(c2))
                c1_trim = c1[:min_len]
                c2_trim = c2[:min_len]
                # 使用归一化的欧氏距离
                distance = np.sqrt(np.mean((c1_trim - c2_trim) ** 2))
                total_distance += weights[i] * distance
                total_weight += weights[i]
            
            # 计算最终距离（不缩放，因为数据已经归一化）
            final_distance = total_distance / total_weight if total_weight > 0 else total_distance
            
            # 添加调试信息
            print(f"DWT距离计算(半音归一化): 原始={total_distance:.6f}, 最终={final_distance:.6f}")
            
            return {
                'distance': final_distance,
                'wavelet': self.wavelet,
                'levels': self.levels,
                'mode': mode,
                'success': True
            }
            
        except Exception as e:
            return {
                'distance': float('inf'),
                'error': str(e),
                'success': False
            }

class MainWindow:
    def __init__(self, root):
        self.root = root
        self.root.title("普通话声调评分工具")
        self.root.geometry("1200x800")
        
        # 初始化真实算法组件
        if not DEPENDENCIES_OK:
            messagebox.showerror("错误", "缺少必要的依赖包，程序无法运行。\n请安装：pip install -r requirements.txt")
            root.quit()
            return
            
        # 初始化算法组件
        self.praat_processor = PraatProcessor()
        self.curve_fitter = ToneCurveFitter()
        # 优化DWT参数：使用更平滑的小波和2层分解
        self.dwt_calculator = DWTCalculator(wavelet='coif2', levels=2)  # coif2对噪音更鲁棒，2层分解
        self.recorder = AudioRecorder()  # 初始化录音器
        self.audio_player = AudioPlayer()  # 初始化音频播放器
        
        # 数据存储
        self.standard_audio_info = None
        self.standard_data = None
        self.user_recordings = []
        self.user_audio_path = None
        
        # UI状态
        self.is_recording = False
        self.is_standard_recording = False
        self.standard_recording_path = None
        
        self.setup_ui()
        
    def setup_styles(self):
        """配置UI样式"""
        style = ttk.Style()
        
        # 配置LabelFrame标题的字体样式
        style.configure("Title.TLabelframe.Label", 
                       font=('Microsoft YaHei', 12, 'bold'),
                       foreground='navy')
        
    def setup_ui(self):
        """设置用户界面"""
        # 配置样式
        self.setup_styles()
        
        # 创建主框架
        main_frame = ttk.Frame(self.root)
        main_frame.pack(fill=tk.BOTH, expand=True, padx=10, pady=10)
        
        # 左侧控制面板
        self.create_control_panel(main_frame)
        
        # 右侧图表区域
        self.create_chart_area(main_frame)
        
        # 底部状态栏
        self.create_status_bar(main_frame)
    
    def create_control_panel(self, parent):
        """创建左侧控制面板"""
        control_frame = ttk.Frame(parent)
        control_frame.pack(side=tk.LEFT, fill=tk.Y, padx=(0, 10))
        
        # 标准音频选择区域
        std_frame = ttk.LabelFrame(control_frame, text="标准音频", padding=10)
        std_frame.configure(style="Title.TLabelframe")
        std_frame.pack(fill=tk.X, pady=(0, 10))
        
        # 文件选择按钮
        ttk.Button(std_frame, text="选择标准音频文件", 
                  command=self.select_standard_audio).pack(fill=tk.X, pady=(0, 3))
        
        # 录制标准音频按钮
        self.std_record_btn = ttk.Button(std_frame, text="录制标准音频", 
                                        command=self.toggle_standard_recording)
        self.std_record_btn.pack(fill=tk.X, pady=(0, 5))
        
        # 拼音和声调输入区域
        input_frame = ttk.Frame(std_frame)
        input_frame.pack(fill=tk.X, pady=(0, 5))
        
        # 拼音输入
        pinyin_frame = ttk.Frame(input_frame)
        pinyin_frame.pack(fill=tk.X, pady=(0, 2))
        ttk.Label(pinyin_frame, text="拼音:", width=6).pack(side=tk.LEFT)
        self.pinyin_var = tk.StringVar()
        self.pinyin_var.trace('w', lambda *args: self.update_reading_display())
        self.pinyin_entry = ttk.Entry(pinyin_frame, textvariable=self.pinyin_var, width=8)
        self.pinyin_entry.pack(side=tk.LEFT, padx=(5, 0))
        
        # 声调选择
        tone_frame = ttk.Frame(input_frame)
        tone_frame.pack(fill=tk.X, pady=(0, 2))
        ttk.Label(tone_frame, text="声调:", width=6).pack(side=tk.LEFT)
        
        self.tone_var = tk.StringVar(value="1")
        tone_combo = ttk.Combobox(tone_frame, textvariable=self.tone_var,
                                 values=["1", "2", "3", "4"],
                                 state="readonly", width=5)
        tone_combo.pack(side=tk.LEFT, padx=(5, 0))
        tone_combo.bind('<<ComboboxSelected>>', self.on_tone_changed)
        
        # 大字体朗读信息显示区域
        reading_frame = ttk.LabelFrame(std_frame, text="朗读信息", padding=5)
        reading_frame.pack(fill=tk.X, pady=(5, 0))
        
        self.reading_info_label = ttk.Label(reading_frame, text="", 
                                          font=('Microsoft YaHei', 16, 'bold'),
                                          foreground="blue",
                                          justify=tk.CENTER)
        self.reading_info_label.pack(fill=tk.X)
        
        self.std_record_status_label = ttk.Label(std_frame, text="", 
                                               foreground="green")
        self.std_record_status_label.pack(fill=tk.X)
        
        # 播放和查看按钮区域
        std_buttons_frame = ttk.Frame(std_frame)
        std_buttons_frame.pack(fill=tk.X, pady=(5, 0))
        
        # 播放标准音频按钮
        self.play_standard_btn = ttk.Button(std_buttons_frame, text="播放标准音频", 
                                           command=self.play_standard_audio)
        self.play_standard_btn.pack(side=tk.LEFT, fill=tk.X, expand=True, padx=(0, 3))
        
        # 查看标准音频原始基频序列按钮
        ttk.Button(std_buttons_frame, text="查看原始基频序列", 
                  command=self.show_standard_raw_f0_sequence).pack(side=tk.LEFT, fill=tk.X, expand=True, padx=(3, 0))
        
        # 用户音频控制区域
        user_frame = ttk.LabelFrame(control_frame, text="用户音频", padding=10)
        user_frame.configure(style="Title.TLabelframe")
        user_frame.pack(fill=tk.X, pady=(0, 10))
        
        ttk.Button(user_frame, text="选择用户音频文件", 
                  command=self.select_user_audio).pack(fill=tk.X, pady=(0, 5))
        
        self.record_btn = ttk.Button(user_frame, text="开始录音", 
                                    command=self.toggle_recording)
        self.record_btn.pack(fill=tk.X, pady=(0, 5))
        
        # 播放和查看按钮区域
        user_buttons_frame = ttk.Frame(user_frame)
        user_buttons_frame.pack(fill=tk.X, pady=(0, 5))
        
        # 播放用户音频按钮
        self.play_user_btn = ttk.Button(user_buttons_frame, text="播放用户音频", 
                                       command=self.play_user_audio)
        self.play_user_btn.pack(side=tk.LEFT, fill=tk.X, expand=True, padx=(0, 3))
        
        # 原始基频序列按钮
        ttk.Button(user_buttons_frame, text="查看原始基频序列", 
                  command=self.show_raw_f0_sequence).pack(side=tk.LEFT, fill=tk.X, expand=True, padx=(3, 0))
        
        self.user_audio_label = ttk.Label(user_frame, text="未选择用户音频", 
                                         foreground="gray")
        self.user_audio_label.pack(fill=tk.X, pady=(0, 3))
        
        self.record_status_label = ttk.Label(user_frame, text="就绪", 
                                           foreground="green")
        self.record_status_label.pack(fill=tk.X)
        
        # 分析控制区域
        analysis_frame = ttk.LabelFrame(control_frame, text="分析控制", padding=10)
        analysis_frame.configure(style="Title.TLabelframe")
        analysis_frame.pack(fill=tk.X, pady=(0, 10))
        
        ttk.Button(analysis_frame, text="处理标准音频", 
                  command=self.process_standard_audio).pack(fill=tk.X, pady=(0, 3))
        
        ttk.Button(analysis_frame, text="分析当前录音", 
                  command=self.analyze_current_recording).pack(fill=tk.X, pady=(0, 3))
        
        ttk.Button(analysis_frame, text="更新图表", 
                  command=self.update_chart).pack(fill=tk.X, pady=(0, 3))
        
        # 历史记录区域
        history_frame = ttk.LabelFrame(control_frame, text="历史记录", padding=10)
        history_frame.configure(style="Title.TLabelframe")
        history_frame.pack(fill=tk.BOTH, expand=True)
        
        # 历史记录列表
        self.history_tree = ttk.Treeview(history_frame, 
                                        columns=("录音", "距离", "相似度"), 
                                        show="tree headings", height=8)
        
        self.history_tree.heading("#0", text="时间")
        self.history_tree.heading("录音", text="录音")
        self.history_tree.heading("距离", text="DWT距离")
        self.history_tree.heading("相似度", text="相似度")
        
        self.history_tree.column("#0", width=80)
        self.history_tree.column("录音", width=60)
        self.history_tree.column("距离", width=80)
        self.history_tree.column("相似度", width=80)
        
        self.history_tree.pack(fill=tk.BOTH, expand=True, pady=(0, 5))
        
        # 绑定右键菜单和双击事件
        self.history_tree.bind("<Button-3>", self.on_history_right_click)  # 右键
        self.history_tree.bind("<Double-1>", self.on_history_double_click)  # 双击
        
        # 创建右键菜单
        self.history_menu = tk.Menu(self.root, tearoff=0)
        self.history_menu.add_command(label="删除此条记录", command=self.delete_selected_record)
        self.history_menu.add_separator()
        self.history_menu.add_command(label="清空所有记录", command=self.clear_history)
        
        # 历史记录操作按钮
        history_btn_frame = ttk.Frame(history_frame)
        history_btn_frame.pack(fill=tk.X)
        
        ttk.Button(history_btn_frame, text="清空", 
                  command=self.clear_history).pack(side=tk.LEFT, padx=(0, 5))
        ttk.Button(history_btn_frame, text="保存", 
                  command=self.save_session).pack(side=tk.LEFT)
    
    def create_chart_area(self, parent):
        """创建右侧图表区域"""
        chart_frame = ttk.Frame(parent)
        chart_frame.pack(side=tk.RIGHT, fill=tk.BOTH, expand=True)
        
        # 创建两个子图：原始基频图和归一化图
        self.fig, (self.ax1, self.ax2) = plt.subplots(2, 1, figsize=(8, 10))
        self.fig.suptitle('普通话声调分析', fontsize=14, fontweight='bold')
        
        # 上图：原始基频值
        self.ax1.set_xlabel('时间 (归一化)')
        self.ax1.set_ylabel('基频 (Hz)')
        self.ax1.set_title('原始基频对比')
        self.ax1.grid(True, alpha=0.3)
        self.ax1.set_ylim(80, 320)
        
        # 下图：半音归一化基频值
        self.ax2.set_xlabel('时间 (归一化)')
        self.ax2.set_ylabel('相对音高 (半音)')
        self.ax2.set_title('半音归一化基频对比 (趋势分析)')
        self.ax2.grid(True, alpha=0.3)
        self.ax2.set_ylim(-2, 6)   # 固定范围-2到6半音，便于区分四个声调
        
        # 调整子图间距
        self.fig.subplots_adjust(hspace=0.4, right=0.75)
        
        # 嵌入到tkinter中
        self.canvas = FigureCanvasTkAgg(self.fig, chart_frame)
        self.canvas.get_tk_widget().pack(fill=tk.BOTH, expand=True)
        
        # 初始化示例图表
        self.init_demo_chart()
    
    def create_status_bar(self, parent):
        """创建底部状态栏"""
        status_frame = ttk.Frame(parent)
        status_frame.pack(side=tk.BOTTOM, fill=tk.X, pady=(10, 0))
        
        self.status_label = ttk.Label(status_frame, text="程序已启动 - 请选择标准音频文件")
        self.status_label.pack(side=tk.LEFT)
        
        # 进度条
        self.progress = ttk.Progressbar(status_frame, mode='indeterminate')
        self.progress.pack(side=tk.RIGHT, padx=(10, 0))
    
    def init_demo_chart(self):
        """初始化示例图表"""
        x = np.linspace(0, 1, 20)
        
        # 生成示例数据
        base_freq = self.get_base_frequency()
        
        # 一声示例 - 添加自然波动
        freq_range = 50  # 固定频率范围
        y_std_base = base_freq + freq_range * 0.1 + freq_range * 0.05 * x
        y_std = y_std_base + np.random.normal(0, freq_range * 0.01, 20)  # 标准曲线也有轻微波动
        y_user = y_std + np.random.normal(0, freq_range * 0.02, 20)  # 用户示例偏差稍大
        
        # 原始基频图（上图）
        self.ax1.plot(x, y_std, 'b-', linewidth=3, label='标准曲线示例', alpha=0.8)
        self.ax1.plot(x, y_user, 'r-', linewidth=2, label='用户录制示例', alpha=0.7)
        self.ax1.legend(bbox_to_anchor=(1.05, 1), loc='upper left')
        
        # 半音归一化基频图（下图）
        y_std_norm = self.curve_fitter.normalize_to_semitones(y_std)
        y_user_norm = self.curve_fitter.normalize_to_semitones(y_user)
        
        self.ax2.plot(x, y_std_norm, 'b-', linewidth=3, label='标准曲线示例（半音）', alpha=0.8)
        self.ax2.plot(x, y_user_norm, 'r-', linewidth=2, label='用户录制示例（半音）', alpha=0.7)
        self.ax2.legend(bbox_to_anchor=(1.05, 1), loc='upper left')
        
        # 调整布局并绘制
        self.fig.tight_layout()
        self.canvas.draw()
    
    def update_status(self, message):
        """更新状态栏"""
        self.status_label.config(text=message)
        self.root.update_idletasks()
    
    
    def on_tone_changed(self, event=None):
        """声调选择变化时的回调"""
        if hasattr(self, 'standard_audio_info') and self.standard_audio_info:
            # 更新标准音频信息中的声调
            tone_str = self.tone_var.get()
            if tone_str and tone_str.isdigit():
                self.standard_audio_info['tone'] = int(tone_str)
                print(f"声调已更新为: {tone_str}声")
                self.update_status(f"声调已更新为: {tone_str}声")
                # 更新朗读信息显示
                self.update_reading_display()
    
    def update_reading_display(self):
        """更新朗读信息显示"""
        if not hasattr(self, 'standard_audio_info') or not self.standard_audio_info:
            self.reading_info_label.config(text="")
            return
        
        # 获取拼音和声调信息
        pinyin_text = self.pinyin_var.get().strip() if hasattr(self, 'pinyin_var') else self.standard_audio_info.get('pinyin', '')
        tone_text = self.tone_var.get().strip() if hasattr(self, 'tone_var') else str(self.standard_audio_info.get('tone', ''))
        
        if pinyin_text and tone_text:
            # 只显示拼音和声调
            display_text = f"{pinyin_text} ({tone_text}声)"
            self.reading_info_label.config(text=display_text, foreground="blue")
        else:
            self.reading_info_label.config(text="", foreground="gray")
    
    def get_base_frequency(self):
        """获取基准基频"""
        return 170  # 返回一个中等基频值作为基准
    
    def update_praat_parameters(self):
        """更新Praat参数"""
        # 设置统一的基频范围用于提取
        self.praat_processor.f0_min = 50   # 最低基频
        self.praat_processor.f0_max = 600  # 最高基频
        print(f"更新Praat参数: f0_min={self.praat_processor.f0_min}Hz, f0_max={self.praat_processor.f0_max}Hz")
    
    def parse_audio_filename(self, filename):
        """解析音频文件名"""
        import re
        basename = os.path.splitext(filename)[0]
        print(f"解析文件名: {basename}")
        
        # 支持多种格式
        patterns = [
            # 格式1: 1_082_1_bi3_b_i3
            r'1_(\d+)_1_([a-z]+\d)_([a-z])_([a-z]+\d)',
            # 格式2: 1_编号_1_拼音声调_0_拼音声调  
            r'1_(\d+)_1_([a-z]+\d)_0_([a-z]+\d)',
        ]
        
        for i, pattern in enumerate(patterns):
            match = re.match(pattern, basename)
            if match:
                print(f"匹配模式{i+1}: {match.groups()}")
                
                if i == 0:  # 格式1: 1_082_1_bi3_b_i3
                    file_id, pinyin_tone1, separator, pinyin_tone2 = match.groups()
                    # 从第一个拼音声调中提取信息
                    pinyin_match = re.match(r'([a-z]+)(\d)', pinyin_tone1)
                    if pinyin_match:
                        pinyin_base, tone_str = pinyin_match.groups()
                        result = {
                            'filename': filename,
                            'file_id': file_id,
                            'pinyin': pinyin_base,
                            'tone': int(tone_str),
                            'full_code': pinyin_tone1
                        }
                        print(f"解析结果: {result}")
                        return result
                        
                elif i == 1:  # 格式2: 原始格式
                    file_id, pinyin_tone1, pinyin_tone2 = match.groups()
                    pinyin_match = re.match(r'([a-z]+)(\d)', pinyin_tone1)
                    if pinyin_match:
                        pinyin_base, tone_str = pinyin_match.groups()
                        result = {
                            'filename': filename,
                            'file_id': file_id,
                            'pinyin': pinyin_base,
                            'tone': int(tone_str),
                            'full_code': pinyin_tone1
                        }
                        print(f"解析结果: {result}")
                        return result
        
        print(f"无法解析文件名: {basename}")
        return None
    
    def select_standard_audio(self):
        """选择标准音频文件"""
        file_path = filedialog.askopenfilename(
            title="选择标准音频文件",
            filetypes=[("WAV文件", "*.wav"), ("所有文件", "*.*")]
        )
        
        if file_path:
            filename = os.path.basename(file_path)
            parsed_info = self.parse_audio_filename(filename)
            
            if parsed_info:
                self.standard_audio_info = parsed_info
                self.standard_audio_info['full_path'] = file_path
                self.standard_audio_path = file_path  # 添加这个变量供查看原始序列功能使用
                
                # 自动填充输入框
                self.pinyin_var.set(parsed_info['pinyin'])
                self.tone_var.set(str(parsed_info['tone']))
                
                info_text = f"{parsed_info['pinyin']} {parsed_info['tone']}声"
                
                # 更新朗读信息显示
                self.update_reading_display()
                
                self.update_status(f"已选择标准音频: {info_text}")
                print(f"标准音频信息设置成功: {self.standard_audio_info}")
            else:
                # 解析失败时的处理
                self.standard_audio_info = {
                    'filename': filename,
                    'file_id': 'unknown',
                    'pinyin': 'unknown',
                    'tone': 1,  # 默认一声
                    'full_code': 'unknown',
                    'full_path': file_path
                }
                self.standard_audio_path = file_path  # 解析失败时也要设置路径
                
                # 清空输入框，让用户手动输入
                self.pinyin_var.set("")
                self.tone_var.set("1")  # 默认设置为一声
                
                # 更新朗读信息显示
                self.update_reading_display()
                
                self.update_status(f"已选择音频文件: {filename} (请手动输入拼音、声调)")
                print(f"文件名解析失败，使用默认设置: {self.standard_audio_info}")
    
    def select_user_audio(self):
        """选择用户音频文件"""
        file_path = filedialog.askopenfilename(
            title="选择用户音频文件",
            filetypes=[("WAV文件", "*.wav"), ("MP3文件", "*.mp3"), ("所有文件", "*.*")]
        )
        
        if file_path:
            self.user_audio_path = file_path
            filename = os.path.basename(file_path)
            self.user_audio_label.config(text=f"已选择: {filename}", foreground="black")
            self.update_status(f"已选择用户音频: {filename}")
            messagebox.showinfo("成功", f"已选择用户音频文件:\n{filename}")

    def show_standard_raw_f0_sequence(self):
        """显示标准音频的原始基频序列"""
        if not hasattr(self, 'standard_audio_path') or not self.standard_audio_path:
            messagebox.showwarning("警告", "请先选择标准音频文件")
            return
        
        try:
            # 使用PraatProcessor提取基频
            time_array, f0_array = self.praat_processor.extract_f0_contour(self.standard_audio_path)
            
            if len(f0_array) == 0:
                messagebox.showerror("错误", "无法从标准音频中提取基频数据")
                return
            
            # 创建新窗口显示原始基频序列
            self.create_f0_sequence_window(time_array, f0_array, "标准音频原始基频序列")
            
        except Exception as e:
            messagebox.showerror("错误", f"提取标准音频基频失败: {str(e)}")

    def show_raw_f0_sequence(self):
        """显示用户音频的原始基频序列"""
        if not self.user_audio_path:
            messagebox.showwarning("警告", "请先选择用户音频文件或录制音频")
            return
        
        try:
            self.update_status("正在提取原始基频序列...")
            
            # 提取原始基频序列
            time_array, f0_array = self.praat_processor.extract_f0_contour(self.user_audio_path)
            
            if len(f0_array) == 0:
                messagebox.showerror("错误", "无法提取音频的基频数据")
                self.update_status("基频提取失败")
                return
            
            # 创建新窗口显示原始基频序列
            self.create_f0_sequence_window(time_array, f0_array, "用户音频原始基频序列")
            self.update_status("原始基频序列显示完成")
            
        except Exception as e:
            messagebox.showerror("错误", f"提取基频序列时发生错误: {str(e)}")
            self.update_status("基频序列提取失败")
    
    def create_f0_sequence_window(self, time_array, f0_array, title="原始基频序列"):
        """创建显示基频序列的新窗口"""
        # 创建新窗口
        f0_window = tk.Toplevel(self.root)
        f0_window.title(title)
        f0_window.geometry("800x600")
        f0_window.resizable(True, True)
        
        # 创建主框架
        main_frame = ttk.Frame(f0_window, padding=10)
        main_frame.pack(fill=tk.BOTH, expand=True)
        
        # 标题
        title_label = ttk.Label(main_frame, text="用户音频原始基频序列", 
                               font=('Arial', 14, 'bold'))
        title_label.pack(pady=(0, 10))
        
        # 统计信息
        stats_frame = ttk.LabelFrame(main_frame, text="统计信息", padding=10)
        stats_frame.pack(fill=tk.X, pady=(0, 10))
        
        # 计算统计信息
        f0_min, f0_max = np.min(f0_array), np.max(f0_array)
        f0_mean, f0_std = np.mean(f0_array), np.std(f0_array)
        duration = time_array[-1] - time_array[0] if len(time_array) > 1 else 0
        
        stats_text = f"""
数据点数: {len(f0_array)} 个
时长: {duration:.3f} 秒
基频范围: {f0_min:.1f} - {f0_max:.1f} Hz
平均基频: {f0_mean:.1f} Hz
标准差: {f0_std:.2f} Hz
        """.strip()
        
        ttk.Label(stats_frame, text=stats_text, font=('Consolas', 10)).pack(anchor=tk.W)
        
        # 数据表格框架
        table_frame = ttk.LabelFrame(main_frame, text="基频数据", padding=10)
        table_frame.pack(fill=tk.BOTH, expand=True)
        
        # 创建表格
        columns = ('序号', '时间(s)', '基频(Hz)')
        tree = ttk.Treeview(table_frame, columns=columns, show='headings', height=15)
        
        # 定义列标题
        tree.heading('序号', text='序号')
        tree.heading('时间(s)', text='时间(s)')
        tree.heading('基频(Hz)', text='基频(Hz)')
        
        # 设置列宽
        tree.column('序号', width=80, anchor=tk.CENTER)
        tree.column('时间(s)', width=120, anchor=tk.CENTER)
        tree.column('基频(Hz)', width=120, anchor=tk.CENTER)
        
        # 添加滚动条
        scrollbar_y = ttk.Scrollbar(table_frame, orient=tk.VERTICAL, command=tree.yview)
        scrollbar_x = ttk.Scrollbar(table_frame, orient=tk.HORIZONTAL, command=tree.xview)
        tree.configure(yscrollcommand=scrollbar_y.set, xscrollcommand=scrollbar_x.set)
        
        # 填充数据
        for i, (time_val, f0_val) in enumerate(zip(time_array, f0_array)):
            tree.insert('', 'end', values=(i+1, f'{time_val:.4f}', f'{f0_val:.2f}'))
        
        # 布局表格和滚动条
        tree.pack(side=tk.LEFT, fill=tk.BOTH, expand=True)
        scrollbar_y.pack(side=tk.RIGHT, fill=tk.Y)
        scrollbar_x.pack(side=tk.BOTTOM, fill=tk.X)
        
        # 按钮框架
        button_frame = ttk.Frame(main_frame)
        button_frame.pack(fill=tk.X, pady=(10, 0))
        
        # 导出CSV按钮
        def export_csv():
            """导出CSV文件"""
            filename = filedialog.asksaveasfilename(
                title="保存基频数据",
                defaultextension=".csv",
                filetypes=[("CSV文件", "*.csv"), ("所有文件", "*.*")]
            )
            if filename:
                try:
                    import csv
                    with open(filename, 'w', newline='', encoding='utf-8') as csvfile:
                        writer = csv.writer(csvfile)
                        writer.writerow(['序号', '时间(s)', '基频(Hz)'])
                        for i, (time_val, f0_val) in enumerate(zip(time_array, f0_array)):
                            writer.writerow([i+1, f'{time_val:.4f}', f'{f0_val:.2f}'])
                    messagebox.showinfo("成功", f"基频数据已导出到:\n{filename}")
                except Exception as e:
                    messagebox.showerror("错误", f"导出失败: {str(e)}")
        
        ttk.Button(button_frame, text="导出CSV", command=export_csv).pack(side=tk.LEFT, padx=(0, 10))
        ttk.Button(button_frame, text="关闭", command=f0_window.destroy).pack(side=tk.RIGHT)
    
    def toggle_recording(self):
        """切换录音状态"""
        if not self.is_recording:
            self.start_recording()
        else:
            self.stop_recording()
    
    def start_recording(self):
        """开始录音"""
        if not DEPENDENCIES_OK:
            messagebox.showwarning("警告", "录音功能需要安装sounddevice库")
            return
            
        self.is_recording = True
        self.record_btn.config(text="停止录音")
        self.record_status_label.config(text="录音中...", foreground="red")
        self.update_status("正在录音...")
        
        # 开始真实录音
        try:
            self.recorder.start_recording()
            print("录音已开始")
        except Exception as e:
            print(f"录音启动失败: {e}")
            messagebox.showerror("错误", f"录音启动失败: {e}")
            self.is_recording = False
            self.record_btn.config(text="开始录音")
            self.record_status_label.config(text="未录音", foreground="gray")
    
    def auto_stop_recording(self):
        """自动停止录音（演示用）"""
        # 已删除自动停止功能，改为手动停止
        pass
    
    def stop_recording(self):
        """停止录音"""
        if not self.is_recording:
            return
            
        self.is_recording = False
        self.record_btn.config(text="开始录音")
        
        try:
            # 停止录音并获取文件路径
            audio_file = self.recorder.stop_recording()
            if audio_file and os.path.exists(audio_file):
                self.user_audio_file = audio_file
                self.user_audio_path = audio_file  # 设置用户音频路径
                filename = os.path.basename(audio_file)
                
                # 更新界面显示
                self.record_status_label.config(text="录音完成", foreground="green")
                self.user_audio_label.config(text=f"录音完成: {filename}", foreground="green")
                
                self.update_status(f"录音完成: {filename}")
                print(f"录音文件保存为: {audio_file}")
            else:
                self.record_status_label.config(text="录音失败", foreground="red")
                self.update_status("录音失败")
                print("录音文件保存失败")
        except Exception as e:
            print(f"录音停止失败: {e}")
            messagebox.showerror("错误", f"录音停止失败: {e}")
            self.record_status_label.config(text="录音失败", foreground="red")
    
    def process_standard_audio(self):
        """处理标准音频"""
        if not self.standard_audio_info:
            messagebox.showwarning("警告", "请先选择标准音频文件")
            return
        
        self.update_status("正在处理标准音频...")
        
        # 模拟处理过程
        def process_worker():
            try:
                # 更新Praat参数
                self.update_praat_parameters()
                
                # 真实音频处理
                audio_path = self.standard_audio_info['full_path']
                tone = int(self.tone_var.get().split()[0])
                
                self.root.after(0, lambda: self.update_status("正在提取标准音频基频..."))
                
                # 使用Praat提取基频
                time_array, f0_array = self.praat_processor.extract_f0_contour(audio_path)
                
                if len(f0_array) == 0:
                    self.root.after(0, lambda: self.update_status("提取基频失败"))
                    self.root.after(0, lambda: messagebox.showerror("错误", "无法提取标准音频的基频"))
                    return
                
                self.root.after(0, lambda: self.update_status("正在拟合标准曲线..."))
                
                # 曲线拟合
                print(f"标准音频拟合: 使用{tone}声 (二次函数)")
                fitting_result = self.curve_fitter.fit_tone_curve(time_array, f0_array, tone)
                
                if not fitting_result['success']:
                    self.root.after(0, lambda: self.update_status("曲线拟合失败"))
                    self.root.after(0, lambda: messagebox.showerror("错误", f"标准音频曲线拟合失败: {fitting_result.get('error', '未知错误')}"))
                    return
                
                # 生成采样点（使用新的最优采样策略）
                sample_result = self.curve_fitter.generate_sample_points(fitting_result, 20)
                if isinstance(sample_result, dict):
                    sample_points = sample_result['sample_points']
                else:
                    sample_points = sample_result  # 向后兼容
                
                # 保存标准数据
                self.standard_data = {
                    'audio_info': self.standard_audio_info,
                    'time_array': time_array,
                    'f0_array': f0_array,
                    'fitting_result': fitting_result,
                    'sample_points': sample_points,
                    'tone': tone
                }
                
                self.root.after(0, lambda: self.update_status(f"标准音频处理完成 - R²={fitting_result.get('r_squared', 0):.3f}"))
                self.root.after(0, self.update_chart)
                
            except Exception as e:
                self.root.after(0, lambda: self.update_status(f"处理失败: {str(e)}"))
                self.root.after(0, lambda: messagebox.showerror("错误", f"处理标准音频时发生错误: {str(e)}"))
        
        threading.Thread(target=process_worker, daemon=True).start()
    
    def analyze_current_recording(self):
        """分析当前录音"""
        if not self.standard_data:
            messagebox.showwarning("警告", "请先处理标准音频")
            return
        
        # 检查是否有用户音频文件
        if not self.user_audio_path:
            messagebox.showwarning("警告", "请先选择用户音频文件或录制音频")
            return
        
        self.update_status("正在分析音频...")
        
        # 分析用户音频文件
        def analyze_worker():
            try:
                # 确保Praat参数是最新的
                self.update_praat_parameters()
                
                # 真实音频分析
                self.root.after(0, lambda: self.update_status("正在提取用户音频基频..."))
                
                # 使用Praat提取用户音频基频
                time_array, f0_array = self.praat_processor.extract_f0_contour(self.user_audio_path)
                
                if len(f0_array) == 0:
                    self.root.after(0, lambda: self.update_status("提取用户音频基频失败"))
                    self.root.after(0, lambda: messagebox.showerror("错误", "无法提取用户音频的基频"))
                    return
                
                self.root.after(0, lambda: self.update_status("正在拟合用户曲线..."))
                
                # 曲线拟合 - 使用与标准音频相同的声调
                tone = self.standard_data['tone']
                print(f"用户音频拟合: 使用{tone}声 (二次函数)")
                fitting_result = self.curve_fitter.fit_tone_curve(time_array, f0_array, tone)
                
                if not fitting_result['success']:
                    self.root.after(0, lambda: self.update_status("用户曲线拟合失败"))
                    self.root.after(0, lambda: messagebox.showerror("错误", f"用户音频曲线拟合失败: {fitting_result.get('error', '未知错误')}"))
                    return
                
                # 生成采样点（使用新的最优采样策略）
                user_sample_result = self.curve_fitter.generate_sample_points(fitting_result, 20)
                if isinstance(user_sample_result, dict):
                    user_sample_points = user_sample_result['sample_points']
                else:
                    user_sample_points = user_sample_result  # 向后兼容
                
                self.root.after(0, lambda: self.update_status("正在计算DWT距离..."))
                
                # 计算真实DWT距离（使用归一化数据）
                # 对标准和用户数据都进行归一化
                # 使用半音归一化进行DWT距离计算
                standard_normalized = self.curve_fitter.normalize_to_semitones(self.standard_data['sample_points'])
                user_normalized = self.curve_fitter.normalize_to_semitones(user_sample_points)
                
                print(f"归一化前: 标准={np.min(self.standard_data['sample_points']):.1f}-{np.max(self.standard_data['sample_points']):.1f}, 用户={np.min(user_sample_points):.1f}-{np.max(user_sample_points):.1f}")
                print(f"归一化后: 标准={np.min(standard_normalized):.3f}-{np.max(standard_normalized):.3f}, 用户={np.min(user_normalized):.3f}-{np.max(user_normalized):.3f}")
                
                dwt_result = self.dwt_calculator.dwt_distance(
                    standard_normalized, 
                    user_normalized
                )
                
                if not dwt_result['success']:
                    self.root.after(0, lambda: self.update_status("DWT距离计算失败"))
                    self.root.after(0, lambda: messagebox.showerror("错误", f"DWT距离计算失败: {dwt_result.get('error', '未知错误')}"))
                    return
                
                # 计算相似度
                distance = dwt_result['distance']
                similarity = max(0, min(100, 100 * np.exp(-distance / 10)))  # 使用指数函数转换为相似度
                
                user_data = {
                    'timestamp': datetime.now().strftime("%H:%M:%S"),
                    'time_array': time_array,
                    'f0_array': f0_array,
                    'fitting_result': fitting_result,
                    'sample_points': user_sample_points,
                    'dwt_result': dwt_result,
                    'distance': distance,
                    'similarity': similarity
                }
                
                self.user_recordings.append(user_data)
                
                # 更新界面
                self.root.after(0, lambda: self.update_history_list(user_data))
                self.root.after(0, lambda: self.update_status(f"分析完成 - DWT距离: {distance:.3f}, 相似度: {similarity:.1f}%, R²: {fitting_result.get('r_squared', 0):.3f}"))
                self.root.after(0, self.update_chart)
                
            except Exception as e:
                self.root.after(0, lambda: self.update_status(f"分析失败: {str(e)}"))
                self.root.after(0, lambda: messagebox.showerror("错误", f"分析音频时发生错误: {str(e)}"))
        
        threading.Thread(target=analyze_worker, daemon=True).start()
    
    def update_history_list(self, user_data):
        """更新历史记录列表"""
        record_id = len(self.user_recordings)
        # 插入记录时，使用record_id作为item的标识
        item = self.history_tree.insert("", "end", 
                                text=user_data['timestamp'],
                                values=(f"录音{record_id}", 
                                       f"{user_data['distance']:.2f}",
                                       f"{user_data['similarity']:.1f}%"))
        # 将record_id存储为item的tag，方便后续查找
        self.history_tree.item(item, tags=(str(record_id-1),))  # 使用0-based索引
    
    def update_chart(self):
        """更新两个图表：原始基频图和归一化图"""
        # 清除两个子图
        self.ax1.clear()
        self.ax2.clear()
        
        # 设置上图（原始基频）
        self.ax1.set_xlabel('时间 (归一化)')
        self.ax1.set_ylabel('基频 (Hz)')
        self.ax1.set_title('原始基频对比')
        self.ax1.set_ylim(80, 320)
        self.ax1.grid(True, alpha=0.3)
        
        # 设置下图（归一化基频）
        self.ax2.set_xlabel('时间 (归一化)')
        self.ax2.set_ylabel('相对音高 (半音)')
        self.ax2.set_title('半音归一化基频对比 (趋势分析)')
        self.ax2.grid(True, alpha=0.3)
        self.ax2.set_ylim(-2, 6)   # 固定范围-2到6半音，便于区分四个声调
        
        # 获取标准曲线的归一化数据
        standard_normalized = None
        if self.standard_data and 'fitting_result' in self.standard_data:
            fitting_result = self.standard_data['fitting_result']
            if fitting_result.get('success'):
                # 原始基频图
                print(f"绘制标准曲线: 数据点数={len(fitting_result['fitted_values'])}, 基频范围={min(fitting_result['fitted_values']):.1f}-{max(fitting_result['fitted_values']):.1f}Hz")
                self.ax1.plot(fitting_result['time_normalized'], fitting_result['fitted_values'], 
                            'b-', linewidth=3, label='标准曲线', alpha=0.8)
                
                # 半音归一化基频图
                standard_normalized = self.curve_fitter.normalize_to_semitones(fitting_result['fitted_values'])
                self.ax2.plot(fitting_result['time_normalized'], standard_normalized, 
                            'b-', linewidth=3, label='标准曲线（半音）', alpha=0.8)
            else:
                print(f"标准曲线拟合失败: {fitting_result.get('error', '未知错误')}")
        
        # 绘制用户录音曲线
        colors = [
            'red', 'green', 'orange', 'purple', 'brown', 'pink', 'cyan', 'magenta', 
            'lime', 'navy', 'gold', 'crimson', 'darkgreen', 'indigo', 'chocolate',
            'deeppink', 'teal', 'violet', 'olive', 'coral'
        ]
        recent_recordings = self.user_recordings[-20:]  # 显示最近20条
        
        for i, user_data in enumerate(recent_recordings):
            color = colors[i % len(colors)]
            
            if 'fitting_result' in user_data:
                fitting_result = user_data['fitting_result']
                if fitting_result.get('success'):
                    # 计算正确的录音编号
                    recording_num = len(self.user_recordings) - len(recent_recordings) + i + 1
                    label = f"录音{recording_num} (DWT:{user_data['distance']:.3f})"
                    
                    # 使用不同的线型来进一步区分
                    linestyle = '-' if i < 10 else '--'  # 前10条用实线，后10条用虚线
                    
                    # 原始基频图
                    self.ax1.plot(fitting_result['time_normalized'], fitting_result['fitted_values'], 
                                color=color, linewidth=2.5, label=label, alpha=0.9, linestyle=linestyle)
                    
                    # 半音归一化基频图
                    user_normalized = self.curve_fitter.normalize_to_semitones(fitting_result['fitted_values'])
                    self.ax2.plot(fitting_result['time_normalized'], user_normalized, 
                                color=color, linewidth=2.5, label=label, alpha=0.9, linestyle=linestyle)
                    
                    print(f"绘制用户曲线: {label}, 颜色: {color}, 线型: {linestyle}")
        
        # 设置图例，放在图的右侧
        legend_fontsize = 8 if len(recent_recordings) > 10 else 10
        legend_ncol = 2 if len(recent_recordings) > 15 else 1
        
        self.ax1.legend(bbox_to_anchor=(1.05, 1), loc='upper left',
                       fontsize=legend_fontsize, ncol=legend_ncol)
        self.ax2.legend(bbox_to_anchor=(1.05, 1), loc='upper left',
                       fontsize=legend_fontsize, ncol=legend_ncol)
        
        # 调整布局以适应图例
        self.fig.tight_layout()
        self.canvas.draw()
    
    def on_history_right_click(self, event):
        """处理历史记录右键点击"""
        # 选中点击的项目
        item = self.history_tree.identify_row(event.y)
        if item:
            self.history_tree.selection_set(item)
            self.history_menu.post(event.x_root, event.y_root)
    
    def on_history_double_click(self, event):
        """处理历史记录双击删除"""
        item = self.history_tree.identify_row(event.y)
        if item:
            self.history_tree.selection_set(item)
            self.delete_selected_record()
    
    def delete_selected_record(self):
        """删除选中的历史记录"""
        selected_items = self.history_tree.selection()
        if not selected_items:
            messagebox.showwarning("警告", "请先选择要删除的记录")
            return
        
        item = selected_items[0]
        tags = self.history_tree.item(item, "tags")
        
        if not tags:
            messagebox.showerror("错误", "无法确定要删除的记录")
            return
        
        try:
            record_index = int(tags[0])
            record_info = self.history_tree.item(item, "values")
            record_name = record_info[0] if record_info else "未知录音"
            
            # 确认删除
            if messagebox.askyesno("确认删除", f"确定要删除 {record_name} 吗？"):
                # 从数据中删除
                if 0 <= record_index < len(self.user_recordings):
                    deleted_record = self.user_recordings.pop(record_index)
                    print(f"删除录音记录: 索引{record_index}, {record_name}")
                    
                    # 从界面中删除
                    self.history_tree.delete(item)
                    
                    # 更新剩余记录的标签和索引
                    self.refresh_history_list()
                    
                    # 更新图表
                    self.update_chart()
                    
                    self.update_status(f"已删除 {record_name}")
                else:
                    messagebox.showerror("错误", "记录索引无效")
        except (ValueError, IndexError) as e:
            messagebox.showerror("错误", f"删除记录时发生错误: {str(e)}")
    
    def refresh_history_list(self):
        """刷新历史记录列表，更新编号"""
        # 清空现有列表
        for item in self.history_tree.get_children():
            self.history_tree.delete(item)
        
        # 重新插入所有记录
        for i, user_data in enumerate(self.user_recordings):
            item = self.history_tree.insert("", "end", 
                                    text=user_data['timestamp'],
                                    values=(f"录音{i+1}", 
                                           f"{user_data['distance']:.2f}",
                                           f"{user_data['similarity']:.1f}%"))
            self.history_tree.item(item, tags=(str(i),))  # 使用0-based索引
    
    def clear_history(self):
        """清空历史记录"""
        if messagebox.askyesno("确认", "确定要清空所有历史记录吗？"):
            self.user_recordings.clear()
            for item in self.history_tree.get_children():
                self.history_tree.delete(item)
            self.update_chart()
            self.update_status("历史记录已清空")
    
    def save_session(self):
        """保存会话数据"""
        if not self.standard_data or not self.user_recordings:
            messagebox.showwarning("警告", "没有可保存的数据")
            return
        
        try:
            # 准备会话数据
            session_data = {
                'timestamp': datetime.now().strftime("%Y-%m-%d %H:%M:%S"),
                'standard_info': self.standard_audio_info,
                'user_records': self.user_recordings
            }
            
            # 保存到JSON文件
            sessions_file = os.path.join("history", "sessions.json")
            
            if os.path.exists(sessions_file):
                with open(sessions_file, 'r', encoding='utf-8') as f:
                    data = json.load(f)
            else:
                data = {'sessions': []}
            
            data['sessions'].append(session_data)
            
            with open(sessions_file, 'w', encoding='utf-8') as f:
                json.dump(data, f, ensure_ascii=False, indent=2)
            
            self.update_status(f"会话已保存 - {len(self.user_recordings)}条记录")
            messagebox.showinfo("成功", f"会话数据已保存")
            
        except Exception as e:
            messagebox.showerror("错误", f"保存会话时发生错误: {str(e)}")

    def toggle_standard_recording(self):
        """切换标准音频录音状态"""
        if not self.is_standard_recording:
            self.start_standard_recording()
        else:
            self.stop_standard_recording()
    
    def start_standard_recording(self):
        """开始录制标准音频"""
        if not DEPENDENCIES_OK:
            messagebox.showwarning("警告", "录音功能需要安装sounddevice库")
            return
            
        self.is_standard_recording = True
        self.std_record_btn.config(text="停止录音")
        self.std_record_status_label.config(text="正在录制标准音频...", foreground="red")
        self.update_status("正在录制标准音频...")
        
        # 开始真实录音
        try:
            self.recorder.start_recording()
            print("标准音频录音已开始")
        except Exception as e:
            print(f"标准音频录音启动失败: {e}")
            messagebox.showerror("错误", f"标准音频录音启动失败: {e}")
            self.is_standard_recording = False
            self.std_record_btn.config(text="录制标准音频")
            self.std_record_status_label.config(text="录音失败", foreground="red")
    
    def stop_standard_recording(self):
        """停止录制标准音频"""
        if not self.is_standard_recording:
            return
            
        self.is_standard_recording = False
        self.std_record_btn.config(text="录制标准音频")
        
        try:
            # 停止录音并获取文件路径
            audio_file = self.recorder.stop_recording()
            if audio_file and os.path.exists(audio_file):
                self.standard_recording_path = audio_file
                filename = os.path.basename(audio_file)
                
                # 更新界面显示
                self.std_record_status_label.config(text=f"录制完成: {filename}", foreground="green")
                self.update_status(f"标准音频录制完成: {filename}")
                print(f"标准音频录制文件: {audio_file}")
                
                # 自动弹出保存对话框
                self.auto_save_standard_recording()
            else:
                self.std_record_status_label.config(text="录音失败", foreground="red")
                self.update_status("标准音频录音失败")
                print("标准音频录音文件保存失败")
        except Exception as e:
            print(f"标准音频录音停止失败: {e}")
            messagebox.showerror("错误", f"标准音频录音停止失败: {e}")
            self.std_record_status_label.config(text="录音失败", foreground="red")
    
    def auto_save_standard_recording(self):
        """自动保存录制的标准音频"""
        if not self.standard_recording_path or not os.path.exists(self.standard_recording_path):
            messagebox.showwarning("警告", "没有可保存的录音文件")
            return
        
        # 弹出输入对话框
        result = self.show_input_dialog()
        if not result:
            # 用户取消了输入
            self.std_record_status_label.config(text="保存已取消", foreground="orange")
            return
        
        pinyin_text, tone_num = result
        
        # 生成文件名：1_编号_1_拼音声调_0_拼音声调.wav
        # 使用时间戳作为编号确保唯一性
        timestamp = datetime.now().strftime("%Y%m%d%H%M%S")
        pinyin_with_tone = f"{pinyin_text}{tone_num}"
        filename = f"1_{timestamp}_1_{pinyin_with_tone}_0_{pinyin_with_tone}.wav"
        
        # 确保目录存在
        save_dir = "standard_audio"
        if not os.path.exists(save_dir):
            os.makedirs(save_dir)
        
        save_path = os.path.join(save_dir, filename)
        
        try:
            # 复制录音文件到标准音频目录
            shutil.copy2(self.standard_recording_path, save_path)
            
            # 创建标准音频信息
            self.standard_audio_info = {
                'filename': filename,
                'file_id': timestamp,
                'pinyin': pinyin_text,
                'tone': tone_num,
                'full_code': pinyin_with_tone,
                'full_path': save_path
            }
            
            self.standard_audio_path = save_path  # 设置标准音频路径供查看原始序列功能使用
            
            # 更新界面显示
            info_text = f"{pinyin_text} {tone_num}声"
            self.pinyin_var.set(pinyin_text)
            self.tone_var.set(str(tone_num))
            
            # 更新朗读信息显示
            self.update_reading_display()
            
            # 清理状态
            self.std_record_status_label.config(text="", foreground="green")
            
            self.update_status(f"标准音频已保存: {info_text}")
            messagebox.showinfo("成功", f"标准音频已保存为:\n{filename}\n\n拼音: {pinyin_text}\n声调: {tone_num}声")
            
            print(f"标准音频保存成功: {save_path}")
            print(f"标准音频信息: {self.standard_audio_info}")
            
        except Exception as e:
            messagebox.showerror("错误", f"保存标准音频失败: {str(e)}")
            print(f"保存标准音频失败: {e}")
    
    def play_standard_audio(self):
        """播放标准音频"""
        if not hasattr(self, 'standard_audio_path') or not self.standard_audio_path:
            messagebox.showwarning("警告", "请先选择标准音频文件")
            return
        
        if not os.path.exists(self.standard_audio_path):
            messagebox.showerror("错误", "标准音频文件不存在")
            return
        
        try:
            # 如果正在播放，则停止播放
            if self.audio_player.is_audio_playing():
                self.audio_player.stop_audio()
                self.play_standard_btn.config(text="播放标准音频")
                self.update_status("标准音频播放已停止")
                return
            
            # 开始播放
            success = self.audio_player.play_audio(self.standard_audio_path)
            if success:
                self.play_standard_btn.config(text="停止播放")
                filename = os.path.basename(self.standard_audio_path)
                self.update_status(f"正在播放标准音频: {filename}")
                
                # 启动一个线程来监控播放状态
                def monitor_playback():
                    import time
                    # 等待播放完成
                    while self.audio_player.is_audio_playing():
                        time.sleep(0.2)
                    
                    # 播放完成后恢复按钮状态
                    self.root.after(0, lambda: self.play_standard_btn.config(text="播放标准音频"))
                    self.root.after(0, lambda: self.update_status("标准音频播放完成"))
                
                threading.Thread(target=monitor_playback, daemon=True).start()
            else:
                messagebox.showerror("错误", "播放标准音频失败")
                
        except Exception as e:
            messagebox.showerror("错误", f"播放标准音频时发生错误: {str(e)}")
            print(f"播放标准音频错误: {e}")
    
    def play_user_audio(self):
        """播放用户音频"""
        if not self.user_audio_path:
            messagebox.showwarning("警告", "请先选择用户音频文件或录制音频")
            return
        
        if not os.path.exists(self.user_audio_path):
            messagebox.showerror("错误", "用户音频文件不存在")
            return
        
        try:
            # 如果正在播放，则停止播放
            if self.audio_player.is_audio_playing():
                self.audio_player.stop_audio()
                self.play_user_btn.config(text="播放用户音频")
                self.update_status("用户音频播放已停止")
                return
            
            # 开始播放
            success = self.audio_player.play_audio(self.user_audio_path)
            if success:
                self.play_user_btn.config(text="停止播放")
                filename = os.path.basename(self.user_audio_path)
                self.update_status(f"正在播放用户音频: {filename}")
                
                # 启动一个线程来监控播放状态
                def monitor_playback():
                    import time
                    # 等待播放完成
                    while self.audio_player.is_audio_playing():
                        time.sleep(0.2)
                    
                    # 播放完成后恢复按钮状态
                    self.root.after(0, lambda: self.play_user_btn.config(text="播放用户音频"))
                    self.root.after(0, lambda: self.update_status("用户音频播放完成"))
                
                threading.Thread(target=monitor_playback, daemon=True).start()
            else:
                messagebox.showerror("错误", "播放用户音频失败")
                
        except Exception as e:
            messagebox.showerror("错误", f"播放用户音频时发生错误: {str(e)}")
            print(f"播放用户音频错误: {e}")

    def show_input_dialog(self):
        """显示拼音和声调输入对话框"""
        dialog = tk.Toplevel(self.root)
        dialog.title("输入拼音和声调")
        dialog.geometry("300x200")
        dialog.resizable(False, False)
        
        # 设置对话框为模态
        dialog.transient(self.root)
        dialog.grab_set()
        
        # 居中显示
        dialog.geometry("+%d+%d" % (self.root.winfo_rootx() + 50, self.root.winfo_rooty() + 50))
        
        result = {'pinyin': '', 'tone': 1, 'confirmed': False}
        
        # 创建输入框
        main_frame = ttk.Frame(dialog, padding=20)
        main_frame.pack(fill=tk.BOTH, expand=True)
        
        # 拼音输入
        ttk.Label(main_frame, text="拼音:", font=('Microsoft YaHei', 10)).pack(anchor=tk.W)
        pinyin_var = tk.StringVar()
        pinyin_entry = ttk.Entry(main_frame, textvariable=pinyin_var, font=('Microsoft YaHei', 10))
        pinyin_entry.pack(fill=tk.X, pady=(5, 15))
        pinyin_entry.focus()
        
        # 声调选择
        ttk.Label(main_frame, text="声调:", font=('Microsoft YaHei', 10)).pack(anchor=tk.W)
        tone_var = tk.StringVar(value="1")
        tone_combo = ttk.Combobox(main_frame, textvariable=tone_var,
                                 values=["1", "2", "3", "4"],
                                 state="readonly", font=('Microsoft YaHei', 10))
        tone_combo.pack(fill=tk.X, pady=(5, 20))
        
        # 按钮框架
        button_frame = ttk.Frame(main_frame)
        button_frame.pack(fill=tk.X)
        
        def on_confirm():
            pinyin_text = pinyin_var.get().strip()
            tone_text = tone_var.get().strip()
            
            if not pinyin_text:
                messagebox.showwarning("警告", "请输入拼音")
                pinyin_entry.focus()
                return
            
            if not tone_text:
                messagebox.showwarning("警告", "请选择声调")
                return
            
            try:
                tone_num = int(tone_text)
                if tone_num not in [1, 2, 3, 4]:
                    raise ValueError("声调必须是1-4")
            except ValueError:
                messagebox.showerror("错误", "声调格式不正确")
                return
            
            result['pinyin'] = pinyin_text
            result['tone'] = tone_num
            result['confirmed'] = True
            dialog.destroy()
        
        def on_cancel():
            dialog.destroy()
        
        ttk.Button(button_frame, text="确定", command=on_confirm).pack(side=tk.RIGHT, padx=(10, 0))
        ttk.Button(button_frame, text="取消", command=on_cancel).pack(side=tk.RIGHT)
        
        # 绑定回车键
        dialog.bind('<Return>', lambda e: on_confirm())
        dialog.bind('<Escape>', lambda e: on_cancel())
        
        # 等待对话框关闭
        dialog.wait_window()
        
        if result['confirmed']:
            return (result['pinyin'], result['tone'])
        else:
            return None

def main():
    root = tk.Tk()
    app = MainWindow(root)
    
    # 程序关闭时停止音频播放
    def on_closing():
        if hasattr(app, 'audio_player'):
            app.audio_player.stop_audio()
        root.destroy()
    
    root.protocol("WM_DELETE_WINDOW", on_closing)
    root.mainloop()

if __name__ == "__main__":
    main()
